<template>
  <div class="pkt-containers">

    <!-- 主体画布 -->
    <div ref="canvas" class="pkt-canvas" />

    <!-- 搜索栏 -->
    <div v-if="isEditModel" class="pkt-search">
      <el-select v-model="selectValue" filterable @change="onSelect">
        <el-option value="ALL" label="所有" />
        <el-option-group
          v-for="group in groups"
          :key="group.value"
          :label="group.label"
        >
          <el-option
            v-for="item in group.options"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        </el-option-group>
      </el-select>
      <el-input v-model="searchValue" placeholder="搜索" @input="onSearch()" />
    </div>
    <!-- 搜索栏END -->

    <!-- 工具栏 -->
    <div class="pkt-toolbar">
      <div class="pkt-controls">
        <li class="pkt-control">
          <button type="button" title="抓手工具" @click="onHandTool">
            <i class="bpmn-icon-hand-tool" style="width:22px;height:22px;" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="拉索工具" @click="onLassoTool">
            <i class="bpmn-icon-lasso-tool" style="width:22px;height:22px;" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="平移工具" @click="onSpaceTool">
            <i class="bpmn-icon-space-tool" style="width:22px;height:22px;" />
          </button>
        </li>
        <li v-if="isEditModel" class="pkt-control pkt-line">
          <button type="button" title="连线工具" @click="onConnectionMulti">
            <i class="bpmn-icon-connection" style="width:22px;height:22px;" />
          </button>
        </li>
        <li v-if="isEditModel" class="pkt-control">
          <input
            ref="file"
            class="pkt-openFile"
            type="file"
            @change="importXmlFile"
          >
          <button type="button" title="打开XML文件" @click="onOpenFile">
            <i class="pkt-open" />
          </button>
        </li>
        <li v-if="isEditModel" class="pkt-control pkt-line">
          <button type="button" title="新建流程" @click="onAdd">
            <i class="pkt-add" />
          </button>
        </li>
        <li v-if="!isPreviewModel" class="pkt-control pkt-line">
          <button type="button" title="保存流程" @click="onSave">
            <i class="pkt-save" />
          </button>
        </li>
        <li v-if="!isPreviewModel" class="pkt-control">
          <button type="button" title="启动预览" @click="onPreview">
            <i class="pkt-preview" />
          </button>
        </li>
        <!-- <li v-if="model!=='preview'" class="pkt-control pkt-line">
        <button type="button" title="启动流程" @click="onStart">
          <i class="pkt-start" style="width:22px;height:22px;" />
        </button>
      </li> -->
        <li v-if="isPreviewModel" class="pkt-control pkt-line">
          <button type="button" title="退出预览" @click="onPreviewOff">
            <i class="pkt-preview-off" />
          </button>
        </li>
        <!-- <li v-if="model==='preview' && starting" class="pkt-control pkt-line">
        <button type="button" title="停止流程" @click="onStop">
          <i class="pkt-stop" style="width:22px;height:22px;" />
        </button>
      </li> -->
        <li v-if="(isShapeElement || isConnectionElement) && isEditModel" class="pkt-control">
          <button type="button" title="删除" @click="onDelete">
            <i class="pkt-delete" />
          </button>
        </li>
        <li v-if="isShapeElement && isEditModel" class="pkt-control">
          <button type="button" title="拷贝" @click="onCopy">
            <i class="pkt-copy" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="撤销" @click="onUndo">
            <i class="pkt-undo" />
          </button>
        </li>
        <li class="pkt-control pkt-line">
          <button type="button" title="恢复" @click="onRedo">
            <i class="pkt-redo" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="自适应窗口" @click="onZoomReset">
            <i class="pkt-zoom" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="放大" @click="onZoomIn">
            <i class="pkt-zoomIn" />
          </button>
        </li>
        <li class="pkt-control pkt-line">
          <button type="button" title="缩小" @click="onZoomOut">
            <i class="pkt-zoomOut" />
          </button>
        </li>
        <li v-if="!isPreviewModel" class="pkt-control">
          <button type="button" title="下载XML文件" @click="onDownloadXml">
            <i class="pkt-download" />
          </button>
        </li>
        <li v-if="!isPreviewModel" class="pkt-control pkt-line">
          <button type="button" title="下载SVG图片" @click="onDownloadSvg">
            <i class="pkt-image" />
          </button>
        </li>
        <li class="pkt-control">
          <button type="button" title="全屏切换" @click="onFullScreen">
            <i class="pkt-fullscreen" />
          </button>
        </li>
      </div>
    </div>
    <!-- 工具栏END -->

    <!-- 参数栏 -->
    <div ref="panel" class="pkt-panel">
      <div
        ref="resize"
        :class="{ hide: zoom !== 0 }"
        class="pkt-panel-resize"
      />
      <div class="pkt-panel-bar">
        <li class="pkt-panel-control">
          <div v-if="zoom === 0">
            <button type="button" title="最大化面板" @click="zoomPanel">
              <i class="pkt-fullscreen" />
            </button>
            <button type="button" title="最小化面板" @click="hoverPanel">
              <i class="pkt-fullscreen-exit" />
            </button>
          </div>
          <div v-else-if="zoom === 1">
            <button type="button" title="缩小面板" @click="zoomPanel">
              <i class="pkt-fullscreen-exit2" />
            </button>
          </div>
          <div v-else-if="zoom === -1">
            <button type="button" title="放大面板" @click="hoverPanel">
              <i class="pkt-fullscreen2" />
            </button>
          </div>
        </li>
      </div>
      <div class="pkt-panel-props">
        <el-backtop target=".pkt-panel-props" />
        <phuket-panel-process
          v-if="isProcessElement"
          :modeler="modeler"
          :process="element"
          :model="model"
        />
        <phuket-panel-shape
          v-if="isShapeElement && !isPreviewModel"
          :modeler="modeler"
          :shape="element"
        />
        <phuket-panel-connection
          v-if="isConnectionElement"
          :modeler="modeler"
          :connection="element"
          :model="model"
        />
        <phuket-panel-preview
          v-if="isShapeElement && isPreviewModel"
          :data="previewDatas[element.id]"
        />
      </div>
    </div>
    <!-- 参数栏END -->

  </div>
</template>

<script>
// 引入bpmn.js的依赖
// import BpmnModeler from 'bpmn-js/lib/Modeler'
// 以下为bpmn工作流绘图工具的样式
import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'

// 引入bpmn.js自定义扩展的依赖
import PhuketModeler from './modeler' // 自定义的Modeler

// 引入相关的依赖
import PhuketPanelProcess from './panel/Process.vue'
import PhuketPanelShape from './panel/Shape.vue'
import PhuketPanelConnection from './panel/Connection.vue'
import PhuketPanelPreview from './panel/Preview.vue'

// 引入全屏插件的依赖
import screenfull from 'screenfull'

import Constant from './Constant' // 常量类
import Common from './Common' // 公共类
import Utils from './Utils' // 工具类
import Shape from './panel/Shape'
import Listener from './Listener' // 监听器类
import PhuketApi from '@/api/process/phuket'

// 本vue组件的样式（必须放在最后）
import '@/assets/phuket/css/index.css'

export default {
  name: 'Phuket',
  components: {
    PhuketPanelProcess,
    PhuketPanelShape,
    PhuketPanelConnection,
    PhuketPanelPreview
  },
  props: {
    option: {
      type: Object,
      required: false,
      default: () => {}
    }
  },
  data: function() {
    return {
      modeler: null,
      canvas: null,
      model: this.option.model ? this.option.model : Constant.EDIT_MODEL,
      xml: this.option.xml,
      groups: Common.getOptions(),
      selectValue: 'ALL', // 下拉框的值
      searchValue: '', // 输入框的值
      scale: 1, // 流程图比例,
      element: null, // Process、Shape、connection
      zoom: 0, // 0：正常，1：放大，-1：缩小
      previewDatas: {} // 预览的数据集
    }
  },
  computed: {
    isProcessElement() {
      return Utils.isProcessElement(this.element)
    },
    isShapeElement() {
      return Utils.isShapeElement(this.element)
    },
    isConnectionElement() {
      return Utils.isConnectionElement(this.element)
    },
    isPreviewModel() {
      return Utils.isPreviewModel(this.model)
    },
    isEditModel() {
      return Utils.isEditModel(this.model)
    }
  },
  watch: {
    model: function() {
      console.log('model变化')
      this.paletteShowAndHide()
    }
  },
  mounted: function() {
    this.initModeler()
    // 参数面板拖动控制器
    this.dragControllerPanel()
  },
  beforeDestroy: function() {
    this.modeler.destroy()
  },
  methods: {
    initModeler() {
      this.canvas = this.$refs.canvas
      // 生成Modeler实例
      this.modeler = new PhuketModeler({
        container: this.canvas,
        additionalModules: []
      })
      // 调色板样式渲染
      this.paletteStyleRender()
      // 创建新的图表
      this.createNewDiagram()
      // 添加Element点击监听
      this.addElementClickListener()
      // 添加EventBus监听
      this.addEventBusListener()
    },
    // 调色板显示和隐藏
    paletteShowAndHide() {
      const djsPalette = document.getElementsByClassName('djs-palette')
      let display = 'block'
      if (this.model !== 'edit') {
        display = 'none'
      }
      Object.assign(djsPalette[0].style, {
        display: display
      })
    },
    // 调色板样式渲染
    paletteStyleRender() {
      // 任何情况下都需要隐藏工具
      const djsPalette = document.getElementsByClassName('djs-palette')
      Object.assign(djsPalette[0].style, {
        display: 'block'
      })
      const djsPaletteEntries = document.getElementsByClassName(
        'djs-palette-entries'
      )
      for (let i = 0; i < djsPaletteEntries.length; i++) {
        const djsPaletteEntrie = djsPaletteEntries[i]
        const groups = djsPaletteEntrie.children
        for (let l = 0; l < groups.length; l++) {
          const group = groups[l]
          const groupName = group.getAttribute('data-appManager')
          // 如果是工具组则隐藏
          if (groupName === 'tools') {
            Object.assign(group.style, {
              display: 'none'
            })
          }
        }
      }
      // 批量修改图标样式
      const actions = document.getElementsByClassName('pkt-icon')
      for (let i = 0; i < actions.length; i++) {
        const action = actions[i]
        const actionName = action.getAttribute('data-action')
        const stage = actionName
        const palette = Common.getPalette(stage)
        const url = palette.url
        const type = palette.type
        const group = palette.group
        // 自定义背景图，添加到css样式中
        if (url) {
          // 修改图标样式
          Object.assign(action.style, {
            'background-image': 'url(' + url + ')'
          })
          // 添加参数名和参数值
          action.setAttribute('type', type)
          action.setAttribute('group', group)
          // 添加类型的显示标记
          let background = '#f0ad4e'
          let sign = 'P'
          if (type === Constant.TYPE_SOURCE) {
            background = '#5bc0de'
            sign = 'S'
          } else if (type === Constant.TYPE_TARGET) {
            background = '#5cb85c'
            sign = 'T'
          }
          action.innerHTML =
            `<span style="width: 14px;
            height: 14px;
            border-radius: 50%;
            font-size: 11px;
            color: white;
            line-height: 15.5px;
            text-align: center;
            display: block;
            font-weight: bolder;
            background: ` + background + `;
            max-height: 28px;">` + sign + `</span>`
        }
      }
      this.paletteShowAndHide()
    },
    // 创建新的图表
    createNewDiagram() {
      let xml = ''
      if (this.xml && this.xml.length > 0) {
        xml = this.xml
      } else {
        xml = Utils.newProcessXml(this.modeler)
      }
      // 将字符串转换成图显示出来
      this.modeler.importXML(xml, err => {
        if (err) {
          console.error('failed to show diagram', err)
          Utils.notify(this, '失败', '无法显示图表', 'error')
        } else {
          // 清空节点参数面板缓存信息
          Shape.emptyShapePanels()
          // 让图能自适应屏幕并居中
          this.modeler.get('canvas').zoom('fit-viewport')
        }
      })
    },
    // 下拉框选择改变时触发
    onSelect() {
      const selectValue = this.selectValue // 获取下拉框的值
      const djsPaletteEntries = document.getElementsByClassName(
        'djs-palette-entries'
      )
      for (let i = 0; i < djsPaletteEntries.length; i++) {
        const groups = djsPaletteEntries[i].children
        for (let j = 0; j < groups.length; j++) {
          const group = groups[j]
          const groupName = group.getAttribute('data-appManager')
          // 排除工具条目
          if (groupName !== 'tools') {
            const actions = group.getElementsByClassName('entry')
            for (let l = 0; l < actions.length; l++) {
              const action = actions[l]
              const type = action.getAttribute('type')
              const group = action.getAttribute('group')
              let display = 'none'
              if (selectValue === 'ALL') {
                // 所有
                display = 'block'
              } else if (
                selectValue === Constant.TYPE_SOURCE ||
                selectValue === Constant.TYPE_TARGET ||
                selectValue === Constant.TYPE_PROCESSOR
              ) {
                // 类型
                if (selectValue === type) {
                  display = 'block'
                }
              } else {
                // 分组
                if (selectValue === group) {
                  display = 'block'
                }
              }
              Object.assign(action.style, {
                display: display
              })
            }
          }
        }
      }
    },
    // 搜索框输入改变时触发
    onSearch() {
      const searchValue = this.searchValue // 获取输入框的值
      const djsPaletteEntries = document.getElementsByClassName(
        'djs-palette-entries'
      )
      for (let i = 0; i < djsPaletteEntries.length; i++) {
        const groups = djsPaletteEntries[i].children
        for (let j = 0; j < groups.length; j++) {
          const group = groups[j]
          const groupName = group.getAttribute('data-appManager')
          // 排除工具条目
          if (groupName !== 'tools') {
            const actions = group.getElementsByClassName('entry')
            for (let l = 0; l < actions.length; l++) {
              const action = actions[l]
              const actionName = action.getAttribute('data-action')
              const title = action.getAttribute('title')
              let display = 'none'
              if (
                actionName.search(searchValue) !== -1 ||
                title.search(searchValue) !== -1
              ) {
                display = 'block'
              }
              Object.assign(action.style, {
                display: display
              })
            }
          }
        }
      }
    },
    // 导入bpmn xml文件
    importXmlFile(e) {
      const self = this
      const file = e.target.files[0]
      const reader = new FileReader()
      reader.readAsText(file)
      reader.onload = function(event) {
        self.renderDiagram(event.target.result, 'open')
      }
    },
    // 打开文件夹选择
    onOpenFile() {
      var file = this.$refs.file
      file.click()
    },
    // 保存
    onSave() {
      const element = this.modeler._definitions.rootElements[0] // Process
      this.modeler.saveXML({ format: true }, (err, xml) => {
        if (err) {
          console.error('failed to get XML', err)
          Utils.notify(this, '失败', '获取流程XML失败', 'error')
        } else {
          this.option.save(element, xml)
        }
      })
    },
    // 前进
    onRedo() {
      this.modeler.get('commandStack').redo()
    },
    // 后退
    onUndo() {
      this.modeler.get('commandStack').undo()
    },
    // 下载SVG格式
    onDownloadSvg() {
      this.modeler.saveSVG({ format: true }, (err, data) => {
        if (err) {
          console.error('failed to download SVG', err)
          Utils.notify(this, '失败', '下载SVG文件失败', 'error')
        } else {
          this.download('svg', data)
        }
      })
    },
    // 下载XML格式
    onDownloadXml() {
      this.modeler.saveXML({ format: true }, (err, data) => {
        if (err) {
          console.error('failed to download xml', err)
          Utils.notify(this, '失败', '下载XML文件失败', 'error')
        } else {
          this.download('xml', data)
        }
      })
    },
    // 流程图放大
    onZoomIn() {
      this.onZoom(0.1)
    },
    // 流程图缩小
    onZoomOut() {
      this.onZoom(-0.1)
    },
    // 流程图自适应窗口
    onZoomReset() {
      // 让图能自适应窗口
      this.modeler.get('canvas').zoom('fit-viewport', 0.5)
      this.scale = 1
    },
    // 流程图放大缩小
    onZoom(radio) {
      const newScale = !radio
        ? 1.0 // 不输入radio则还原
        : this.scale + radio <= 0.2 // 最小缩小倍数
          ? 0.2
          : this.scale + radio
      this.modeler.get('canvas').zoom(newScale)
      this.scale = newScale
    },
    // 渲染Diagram
    renderDiagram(xml) {
      this.modeler.importXML(xml, err => {
        if (err) {
          console.error('failed to show diagram', err)
          Utils.notify(this, '错误', '无法显示图表', 'error')
        } else {
          // 让图能自适应屏幕
          this.modeler.get('canvas').zoom('fit-viewport')
          Utils.notify(this, '成功', '显示图表成功', 'success')
        }
      })
    },
    // 下载
    download(type, data, name) {
      let dataTrack = ''
      const a = document.createElement('a')
      switch (type) {
        case 'xml':
          dataTrack = 'xml'
          break
        case 'svg':
          dataTrack = 'svg'
          break
        default:
          break
      }
      name = name || `process.${dataTrack}`
      a.setAttribute(
        'href',
        `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(data)}`
      )
      a.setAttribute('target', '_blank')
      a.setAttribute('dataTrack', `diagram:download-${dataTrack}`)
      a.setAttribute('download', name)
      document.body.appendChild(a)
      a.click()
      document.body.removeChild(a)
    },
    // 全屏切换
    onFullScreen() {
      screenfull.toggle()
    },
    // 新建流程
    onAdd() {
      const xml = Utils.newProcessXml(this.modeler)
      this.modeler.importXML(xml, err => {
        if (err) {
          console.error('failed to show new diagram', err)
          Utils.notify(this, '失败', '创建新流程失败', 'error')
        } else {
          Utils.notify(this, '成功', '创建新流程成功', 'success')
        }
      })
    },
    // 抓手工具
    onHandTool() {
      this.modeler.get('handTool').toggle()
    },
    // 拉索工具
    onLassoTool() {
      this.modeler.get('lassoTool').toggle()
    },
    // 平移工具
    onSpaceTool() {
      this.modeler.get('spaceTool').toggle()
    },
    // 连线工具
    onConnectionMulti() {
      this.modeler.get('globalConnect').toggle()
    },
    // 拷贝节点
    onCopy() {
      const modeling = this.modeler.get('modeling')
      const businessObject = this.element.businessObject
      const name = businessObject.name
      const attrs = businessObject.$attrs
      const type = businessObject.$type
      const shape = modeling.createShape(
        { type: type, attrs: attrs },
        { x: 200, y: 200, width: 50, height: 50 },
        this.element,
        1, // 参数含义未知
        { attach: 1 } // 参数含义未知
      )
      modeling.updateLabel(shape, name)
      modeling.updateProperties(shape, attrs)
    },
    // 删除节点或连线
    onDelete() {
      Utils.removeElement(this.modeler, this.element)
      this.element = null
    },
    // 启动预览
    onPreview() {
      this.modeler.saveXML({ format: true }, (err, xml) => {
        if (err) {
          console.error('failed to get XML', err)
          Utils.notify(this, '失败', '获取流程XML失败', 'error')
          return
        }
        const loading = this.$loading({
          lock: true,
          text: '正在执行预览，请等待...',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        })
        PhuketApi.preview(xml).then(obj => {
          if (obj.status === 'COMPLETED') {
            Utils.notify(this, '成功', '流程预览成功', 'success')
            this.model = Constant.PREVIEW_MODEL
            this.previewDatas = obj.data
          } else if (obj.status === 'ERRORS') {
            Utils.notify(this, '失败', obj.messages, 'error')
          } else if (obj.messages) {
            Utils.notify(this, '失败', obj.messages, 'error')
          } else if (obj.status === 'STOP') {
            Utils.notify(this, '停止', '流程预览停止', 'warning')
          }
          loading.close()
        }).catch(() => {
          loading.close()
        })
      })
    },
    // 退出预览
    onPreviewOff() {
      this.model = Constant.EDIT_MODEL
    },
    // 参数面板拖动控制器
    dragControllerPanel() {
      const canvasWidth = this.canvas.offsetWidth
      const resize = this.$refs.resize
      const panel = this.$refs.panel
      // 在resize上按下鼠标时触发
      resize.onmousedown = function(e) {
        const startX = e.clientX // 按下鼠标时鼠标的x坐标
        const panelWidth = panel.offsetWidth // 按下鼠标时参数栏的宽
        // 在document上移动鼠标时触发
        document.onmousemove = function(e) {
          const endX = e.clientX // 移动鼠标时鼠标的x坐标
          const moveLen = endX - startX // 鼠标移动长度(向左为负，向右为正)
          let width = panelWidth - moveLen
          if (width >= canvasWidth) width = canvasWidth
          if (width <= 0) width = 0
          panel.style.width = width + 'px'
        }
        // 在document上放开鼠标时触发
        document.onmouseup = function() {
          document.onmousemove = null
          document.onmouseup = null
          resize.releaseCapture && resize.releaseCapture()
        }
        resize.setCapture && resize.setCapture()
        return false
      }
    },
    // 隐藏和显示参数面板
    hoverPanel: function() {
      this.zoom = this.zoom === 0 ? -1 : 0
      this.$refs.panel.style.width = this.zoom === -1 ? '22px' : '30%'
    },
    // 放大和缩小参数面板
    zoomPanel: function() {
      this.zoom = this.zoom === 0 ? 1 : 0
      this.$refs.panel.style.width = this.zoom === 1 ? '100%' : '30%'
    },
    // 添加EventBus监听
    addEventBusListener() {
      Listener.eventBusListener(this)
    },
    // 添加Element点击监听
    addElementClickListener() {
      // 监听element点击事件
      this.modeler.on('element.click', event => {
        this.element = event.element // Process、Shape、connection
        console.log(this.modeler)
      })
    }
  }
}
</script>
